#pragma once

#include "../DefinesCore.hpp"
#include <string>
#include <vector>
#include <array>
#include <map>
#include <chrono>

CYBERTRON_BEGIN

#define UDP_MAX_MESSAGE_SIZE			0xFFFFFF
#define UDP_MIN_MESSAGE_SIZE			4
#define UDP_MESSAGE_EXPIRE_TIME_MS		500		// expire time in milliseconds
#define UDP_MAX_FRAGMENT_SIZE			65507	//65535 - udp header - ip header
#define UDP_MAX_FRAGMENT_CONTENT_SIZE	65488	//UDP_MAX_FRAGMENT_SIZE - (message header/footer + message sequence/fragmentCount/fragmentId + reservedSpace)

struct UDPMessageFragment {
	std::uint16_t	SequenceId = 0;
	std::uint16_t	FragmentCount = 0;
	std::uint16_t	FragmentId = 0;
	std::string		Fragment;
};
typedef std::map<std::uint16_t, UDPMessageFragment, std::less<std::uint16_t> > UDPMessageFragments;

struct UDPMessage {
	std::uint16_t			SequenceId = 0;
	std::uint16_t			FragmentCount = 0;
	UDPMessageFragments		MessageFragments;
	std::chrono::high_resolution_clock::time_point CreateTime = std::chrono::high_resolution_clock::now();

	bool isMessageReady() const;
	bool isMessageOverdue() const;
	std::string decodeMessage() const;
	bool addMessageFragment(UDPMessageFragment& messageFragment);
};

class SocketUdpCommon {
public:
	// header and footer to recognize our message
	static const std::string& messageHeader();
	static const std::string& messageFooter();

	// Message format definition:
	// MessageSupportSplit = Header(2) + SequenceId(2) + Fragments.Count(2) + FragmentId(2) + Fragments[FragmentId] + Footer(2)
	// while Fragments = split(Content + Content.CRC(4), UDP_MAX_FRAGMENT_CONTENT_SIZE)
	static std::vector<std::string> encodeMessage(const std::string& messageContent, std::uint16_t sequenceId);
	static bool decodeMessage(const std::array<char, UDP_MAX_FRAGMENT_SIZE>& buffer, std::uint16_t messageSize, UDPMessageFragment& messageFragment);

	// Simple encoding that doesn't support split
	// SimpleMessage = Header(2) + messageContent + Footer(2)
	static std::string encodeSimpleMessage(const std::string& messageContent);
	static std::string decodeSimpleMessage(const std::array<char, UDP_MAX_FRAGMENT_SIZE>& buffer, std::uint16_t messageSize);

private:
	static bool checkHeaderAndFooter(const std::array<char, UDP_MAX_FRAGMENT_SIZE>& buffer, std::uint16_t messageSize);
};

CYBERTRON_END
